package cn.kent.high.questions.sdf;

import java.text.ParseException;
import java.text.SimpleDateFormat;

/**
 * 多线程下共用一个SimpleDataFormat实例解析或格式化日期，会导致程序出错
 * 问题复现：
 * 问题产生： SimpleDateFormat线程不安全 -> Calendar不安全 -> 存放日期数据的变量是不安全的，如fields、time等
 */
public class SimpleDateFormatTest {
    private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");

    public static void main(String[] args) {
        for (int i = 0; i < 10; i++) {
            Thread thread = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        sdf.parse("1997-11-11 23:10:59");
                        // parse()方法源码解析：
                        // 1. 解析日期字符串，将解析好的日期数据放到CalendarBuilder的实例calb中
                        // 2. 使用calb设置calender
                        //    进入establish方法
                        // 3. cal.clear清空，重新设置日期对象cal的属性值
                        // 4. 使用calb中的属性设置cal
                        // 5. 返回设置好的cal对象

                        // 问题关键： 多线程使用的是同一个cal对象
                        // 第一种情况：345并不是原子性的，当线程A执行了34，线程B执行了3，线程A开始执行5，会导致拿到的cal为空
                        // 第二种情况：线程A执行了34，线程B执行了34，线程A先执行5，那么拿到的是线程B的cal对象，导致程序错误。
                    } catch (ParseException e) {
                        e.printStackTrace();
                    }
                }
            });
            thread.start();
        }
    }
}

/**
 * 解决方法：
 * 1. 每次使用时new一个SimpleDateFormat实例-> 保证了每个线程拿到属于自己的Calender --> 问题：回收开销很大
 * 2. 让代码345保证原子性 -> 操作：加锁synchronized(sdf)  --> 问题：锁竞争，高并发下系统响应性能下降
 * 3. 使用ThreadLocal -> 每个线程只需要一个sdf实例，相比第一种大大节省了对象的创建和回收开销，并且不需要使多个线程同步
 *      - 代码见{@link cn.kent.high.questions.sdf.SimpleDateFormatTestThreadLocal}
 */
